#pragma once

#include <compl/candy.h>

/* 实现参考 aapcs64 
	https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst
	注意x18 平台特定寄存器，大部分时候不需要保存
	x19-x30需要保存
	x31(sp)单独保存
	浮点寄存器只需要保存v8-v15的低64位(d8-d15)
	fpcr和fpsr作为全局变量无需保存
	fpsr处理浮点异常使用
	fpcr
  */


/* 启用预取 */
#ifdef __APPLE__
	/* APPLE平台启用预取, 骁龙不应该启用 */
	/* FIXME: 应该挪到编译配置中 */
	#define PROMPT_PREFETCH_ENABLE
#endif

/* 目前aarch64编译器都不支持__builin_setjmp */
/* gcc还有问题 */
#define SETJMP_NORMAL_RESUME

#if defined (RESUME_FPU)
	/* 使用慢速resume */
	#ifndef SETJMP_NORMAL_RESUME
		#define SETJMP_NORMAL_RESUME
	#endif
#endif

#define __BUF_FPU	\
/*word_t fpcr;*/	\
/*word_t fpsr;*/	\
double d8;		\
double d9;		\
double d10;		\
double d11;		\
double d12;		\
double d13;		\
double d14;		\
double d15;

#define __SAVE_FPSCR(rega, regb, regp)			\
	"/* store fp control and status */\n		\
	mrs	" #rega ", fpcr\n			"\
	"mrs	" #regb ", fpsr\n			"\
	"stp	" #rega ", " #regb", [ " #regp" ], #16\n"\

#define __SAVE_FPU(rega, regb, regp)			\
	"/* store float registers */\n			"\
	"stp	d8,  d9,  [ " #regp "], #16\n		"\
	"stp	d10, d11, [ " #regp "], #16\n		"\
	"stp	d12, d13, [ " #regp "], #16\n		"\
	"stp	d14, d15, [ " #regp "], #16\n		"

#define __LOAD_FPSCR(rega, regb, regp)		\
	"/* load fp control and status */\n		"\
	"ldp	" #rega ", " #regb", [ " #regp "], #16\n"\
	"msr	fpcr, " #rega "\n			"\
	"msr	fpsr, " #regb "\n			"\

#define __LOAD_FPU(rega, regb, regp)			\
	"/* load float registers */\n			"\
	"ldp	d8,  d9,  [ " #regp "], #16\n		"\
	"ldp	d10, d11, [ " #regp "], #16\n		"\
	"ldp	d12, d13, [ " #regp "], #16\n		"\
	"ldp	d14, d15, [ " #regp "], #16\n		"


/* 如果需要, 请添加fpcr和fpsr的保存和恢复. */
#ifdef RESUME_FPU
	#define BUF_FPU			__BUF_FPU
	#define SAVE_FPU(a, b, p)	__SAVE_FPU(a, b, p)
	#define LOAD_FPU(a, b, p)	__LOAD_FPU(a, b, p)
#else
	#define BUF_FPU
	#define SAVE_FPU(a, b, p)
	#define LOAD_FPU(a, b, p)
#endif

typedef struct resume_fpuctx{
	__BUF_FPU
} resume_fpuctx_t;


#define ST_FPUCTX(fpubuf)		\
resume_fpuctx_t *__fpubuf = fpubuf;	\
asm volatile("	mov x0, %0\n"		\
	__SAVE_FPU(x10, x11, x0)	\
	:				\
	: "r"(__fpubuf)			\
	: "x0", "x10", "x11", "memory");

#define LD_FPUCTX(fpubuf)		\
resume_fpuctx_t *__fpubuf = fpubuf;	\
asm volatile("	mov x0, %0\n"		\
	__LOAD_FPU(x10, x11, x0)	\
	:				\
	: "r"(__fpubuf)			\
	: "x0", "x10", "x11", "memory");

#define RESUME_STCTX(reg)			\
	"mov	x2, sp	/* sp to x2 */		\n\
	stp	x19, x20, ["#reg"], #16		\n\
	stp	x21, x22, ["#reg"], #16		\n\
	stp	x23, x24, ["#reg"], #16		\n\
	stp	x25, x26, ["#reg"], #16		\n\
	stp	x27, x28, ["#reg"], #16		\n\
	stp	x29, x30, ["#reg"], #16 	\n\
	str	x2, ["#reg"], #8		\n\
	"					\
	SAVE_FPU(x3, x4, reg)

#define RESUME_LDCTX(reg)			\
	"ldp	x19, x20, ["#reg"], #16		\n\
	ldp	x21, x22, ["#reg"], #16		\n\
	ldp	x23, x24, ["#reg"], #16		\n\
	ldp	x25, x26, ["#reg"], #16		\n\
	ldp	x27, x28, ["#reg"], #16		\n\
	ldp	x29, x30, ["#reg"], #16 	\n\
	ldr	x2, ["#reg"], #8		\n\
	mov	sp,  x2				\n\
	"					\
	LOAD_FPU(x3, x4, reg)

typedef struct cresume_buf{
	/* word_t x18; */ /* r18应该是不需要恢复的*/
	word_t x19;
	word_t x20;
	word_t x21;
	word_t x22;
	word_t x23;
	word_t x24;
	word_t x25;
	word_t x26;
	word_t x27;
	word_t x28;
	word_t fp;	/*x29*/
	word_t lr;	/*x30*/
	word_t sp;	/*x31*/
	BUF_FPU
} cresume_ctx_t;

word_t __ssetjmp(cresume_ctx_t *buf) asm ("_ssetjmp");
asm ("_ssetjmp:					\n"\
	RESUME_STCTX(x0)			\
	"/* always return zero */		\n\
	mov	x0, #0				\n\
	ret\n");

/* 保存r18 */
// asm ("_ssetjmp:					\n\
// 	stp	x18, x19, [x0], #16		\n\
// 	stp	x20, x21, [x0], #16		\n\
// 	stp	x22, x23, [x0], #16		\n\
// 	stp	x24, x25, [x0], #16		\n\
// 	stp	x26, x27, [x0], #16		\n\
// 	stp	x28, x29, [x0], #16 /* x28 fp */\n\
// 	mov	x10, sp	/* sp to x10 */ 	\n\
// 	stp	x30, x10, [x0], #16 /* lr  sp */\n\
// 	"					\
// 	SAVE_FPU(x11, x12, x0)			\
// 	"/* always return zero */		\n\
// 	mov	x0, #0				\n\
// 	ret\n");


/* 直接return 即可，因为x0 一定不为0 */
/* 跳跃后所有本地现场失效 */
/* 使用x0作为判断, assert buf != NULL */
#define __slongjmp(buf, val)			\
asm ("						\n\
	mov	x0, %0				\n\
	/* mov	x1, %1 */			\n"\
	RESUME_LDCTX(x0)			\
	"/* mov	x0, x1 */			\n\
	ret\n"					\
	:					\
	: "r"(buf), "r"(val)			\
	: "memory");				\
UNREACHABLE()

/* 恢复r18 */
// asm ("						\n\
// 	mov	x0, %0				\n\
// 	/* mov	x1, %1 */			\n\
// 	ldp	x18, x19, [x0], #16		\n\
// 	ldp	x20, x21, [x0], #16		\n\
// 	ldp	x22, x23, [x0], #16		\n\
// 	ldp	x24, x25, [x0], #16		\n\
// 	ldp	x26, x27, [x0], #16		\n\
// 	ldp	x28, x29, [x0], #16 /* x28 fp */\n\
// 	ldp	x30, x10, [x0], #16 /* lr sp */	\n\
// 	mov	sp,  x10			\n\
// 	"					\
// 	LOAD_FPU(x5, x6, x0)			\
// 	"/* mov	x0, x1 */			\n\
// 	ret\n"					\
// 	:					\
// 	: "r"((word_t)(buf)), "r"((word_t)(val))\
// 	: "memory");				\
// UNREACHABLE()


// word_t __ctxsw(cresume_ctx_t *buf, cresume_ctx_t *next_buf) asm ("_ctxsw");
// asm ("_ctxsw:						\n\
// 	mov	x2, sp	/* sp to x2 */ 		\n\
// 	stp	x19, x20, [x0], #16			\n\
// 	stp	x21, x22, [x0], #16			\n\
// 	stp	x23, x24, [x0], #16			\n\
// 	stp	x25, x26, [x0], #16			\n\
// 	stp	x27, x28, [x0], #16			\n\
// 	stp	x29, x30, [x0], #16 /* x28 fp */	\n\
// 	str	x2, [x0], #8 /* save sp */		\n\
// 	"						\
// 	SAVE_FPU(x3, x4, x0)				\
// 	"						\
// 	ldp	x19, x20, [x1], #16			\n\
// 	ldp	x21, x22, [x1], #16			\n\
// 	ldp	x23, x24, [x1], #16			\n\
// 	ldp	x25, x26, [x1], #16			\n\
// 	ldp	x27, x28, [x1], #16			\n\
// 	ldp	x29, x30, [x1], #16 /* x28 fp */	\n\
// 	ldr	x2, [x1], #8				\n\
// 	mov	sp,  x2					\n\
// 	"						\
// 	LOAD_FPU(x3, x4, x1)				\
// 	"ret\n");

word_t __ctxsw(cresume_ctx_t *buf, cresume_ctx_t *next_buf) asm ("_ctxsw");
asm ("_ctxsw:						\n"\
	RESUME_STCTX(x0)				\
	RESUME_LDCTX(x1)				\
	"ret\n");

/* 恢复r18 */
// asm ("_ctxsw:					\n\
// 	mov	x10, sp /* sp to x10 */			\n\
// 	stp	x18, x19, [x0], #16			\n\
// 	stp	x20, x21, [x0], #16			\n\
// 	stp	x22, x23, [x0], #16			\n\
// 	stp	x24, x25, [x0], #16			\n\
// 	stp	x26, x27, [x0], #16			\n\
// 	stp	x28, x29, [x0], #16 /* x28 fp */	\n\
// 	stp	x30, x10, [x0], #16 /* lr and sp */	\n\
// 	"						\
// 	SAVE_FPU(x11, x12, x0)				\
// 	"						\
// 	ldp	x18, x19, [x1], #16			\n\
// 	ldp	x20, x21, [x1], #16			\n\
// 	ldp	x22, x23, [x1], #16			\n\
// 	ldp	x24, x25, [x1], #16			\n\
// 	ldp	x26, x27, [x1], #16			\n\
// 	ldp	x28, x29, [x1], #16 /* x28 and fp */	\n\
// 	ldp	x30, x10, [x1], #16 /* lr and sp */	\n\
// 	mov	sp,  x10				\n\
// 	"						\
// 	LOAD_FPU(x11, x12, x1)				\
// 	"ret\n"						);




#ifdef SETJMP_NORMAL_RESUME
	typedef cresume_ctx_t		fresume_t;
	#define SETJMP(jmpbuf)		__ssetjmp(jmpbuf)
	#define LONGJMP(jmpbuf, val)	do{ __slongjmp(jmpbuf, (word_t)val); }while(0)
#else
	/* 使用builtin_setjmp和longjmp */
	#define SETJMP(jmpbuf)		\
	({ fresume_t *__tmpres; SASSERT(IS_SAMETYPE(__tmpres, (jmpbuf))); \
		__builtin_setjmp((void **)(jmpbuf));})
	/* val只能传1 */
	#define LONGJMP(jmpbuf, val)	\
	{ SASSERT((val) == 1); fresume_t *__tmpres; \
		SASSERT(IS_SAMETYPE(__tmpres, (jmpbuf))); \
		__builtin_longjmp((void **)jmpbuf, (word_t)val); UNREACHABLE(); }

	typedef struct fresume_buf{
		word_t fp;
		word_t pc;
		word_t sp;
	} fresume_t;
#endif

static inline void *get_fresume_sp(fresume_t *t){
	return (void *)t->sp;
}

/* 完整的需要设置一堆确保能unwind, 参考https://github.com/koka-lang/libmprompt */

/* 不管unwind, 直接简单粗暴, 最大化性能 */
#define __setstack_exec(func, arg, sp)			\
asm volatile("						\n\
	mov	sp, %0					\n\
	mov	x0, %1					\n\
	mov	x1, %2					\n\
	br	x1"					\
	:						\
	: "r"(sp), "r"(arg), "r"(func)		\
	: "x0", "x1" /*, "x30", "memory"*/  /*,"cc"*/	\
	);						\
UNREACHABLE()

#define __setstack_exec2(func, arg0, arg1, sp)		\
asm volatile("						\n\
	mov	sp, %0					\n\
	mov	x0, %1					\n\
	mov	x1, %2					\n\
	mov	x2, %3					\n\
	br	x2					\n"\
	:						\
	: "r"(sp), "r"(arg0), "r"(arg1), "r"(func)	\
	: "x0", "x1", "x2", "memory" /*,"cc"*/);	\
UNREACHABLE()

/* 在编译器已知函数func的情况下，避免indirect call(依赖访存结果的call)，防止CPU流水线阻塞  */
#define __setstack_exec2_const(func, arg0, arg1, sp)	\
asm volatile("						\n\
	mov	sp, %0					\n\
	mov	x0, %1					\n\
	mov	x1, %2					\n\
	b	%3					\n"\
	:						\
	: "r"(sp), "r"(arg0), "r"(arg1), "i"(func)	\
	: "x0", "x1" /*,"memory"*//* don't need */);	\
UNREACHABLE()

#define setstack_exec2(func, arg0, arg1, sp)	do{				\
	if(CAN_EVAL(func)){							\
		__setstack_exec2_const(func, (word_t)arg0, (word_t)arg1, sp);	\
	}else{									\
		__setstack_exec2(func, (word_t)arg0, (word_t)arg1, sp);		\
	}									\
}while(0)

#define setstack_exec(func, arg, sp)		do{ __setstack_exec(func, (word_t)arg, sp); }while(0)
